package pers.yurwisher.rubick.manager.service;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import pers.yurwisher.rubick.common.constant.CacheConstant;
import pers.yurwisher.rubick.common.enums.UserClientType;
import pers.yurwisher.rubick.common.enums.tip.SystemCustomTipEnum;
import pers.yurwisher.rubick.common.exception.SystemCustomException;
import pers.yurwisher.rubick.common.security.CustomSecurityProperties;
import pers.yurwisher.rubick.common.security.TokenProvider;
import pers.yurwisher.rubick.common.service.CustomRedisCacheService;
import pers.yurwisher.rubick.common.user.CurrentUser;
import pers.yurwisher.rubick.common.user.CustomAccessToken;
import pers.yurwisher.rubick.common.user.CustomTokenCache;
import pers.yurwisher.rubick.common.user.CustomUserDetails;
import pers.yurwisher.rubick.manager.common.Constants;
import pers.yurwisher.rubick.manager.common.Utils;
import pers.yurwisher.rubick.manager.entity.SystemClient;
import pers.yurwisher.rubick.manager.entity.SystemUser;
import pers.yurwisher.rubick.manager.pojo.auth.AccessTokenForm;
import pers.yurwisher.rubick.manager.pojo.auth.CurrentUserInfo;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;

/**
 * @author yq 2021/4/22 13:42
 * @description AuthorizationService 认证service
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class AuthorizationService {

    private final ISystemClientService systemClientService;
    private final ISystemUserService systemUserService;
    private final CustomRedisCacheService customRedisCacheService;
    private final PasswordEncoder passwordEncoder;
    private final TokenProvider tokenProvider;
    private final IOnlineUserService onlineUserService;

    public CustomAccessToken token(AccessTokenForm form, HttpServletRequest request) {
        SystemClient client = systemClientService.getOneByFieldValueEq(SystemClient::getClientId, form.getClient_id());
        //确保 client 存在
        if (client == null) {
            //无效client
            throw new SystemCustomException(SystemCustomTipEnum.INVALID_CLIENT);
        }
        //确保 clientSecret 正确
        if (!client.getSecret().equals(form.getClient_secret())) {
            //client secret不对
            throw new SystemCustomException(SystemCustomTipEnum.ERROR_CLIENT_SECRET);
        }
        //根据grantType决定登录方式
        if (Constants.GrantType.PASSWORD.equalsIgnoreCase(form.getGrant_type())) {
            //username password必填
            if (StrUtil.isBlank(form.getUsername()) || StrUtil.isBlank(form.getPassword())) {
                throw new SystemCustomException(SystemCustomTipEnum.REQUIRE_USERNAME_AND_PASSWORD);
            }
            //密码模式
            SystemUser systemUser = systemUserService.getUserByUsername(form.getUsername());
            //校验帐号及密码
            if (systemUser == null || !passwordEncoder.matches(form.getPassword(), systemUser.getPassword())) {
                //帐号或密码错误
                throw new SystemCustomException(SystemCustomTipEnum.AUTH_USERNAME_OR_PASSWORD_ERROR);
            }
            //校验帐号状态
            if (systemUser.getState() == 2) {
                //帐号被禁用
                throw new SystemCustomException(SystemCustomTipEnum.AUTH_USERNAME_FORBIDDEN);
            }
            return this.create(systemUser.getId(), request);
        } else {
            //刷新token模式
            if (StrUtil.isBlank(form.getRefresh_token())) {
                //刷新凭证必填
                throw new SystemCustomException(SystemCustomTipEnum.REQUIRE_REFRESH_CREDENTIALS);
            }
            //获取刷新token信息
            CustomTokenCache customTokenCache = customRedisCacheService.get(CacheConstant.Strings.REFRESH_AUTH, form.getRefresh_token());
            //不存在则代表刷新token已过期
            if (customTokenCache == null) {
                throw new SystemCustomException(SystemCustomTipEnum.REFRESH_CREDENTIALS_INVALID);
            }
            return this.refresh(customTokenCache, request);
        }
    }

    private CustomAccessToken create(Long userId, HttpServletRequest request) {
        //创建token
        CustomAccessToken accessToken = this.create(ListUtil.of("API"), null);
        //创建token redis cache对象
        CustomTokenCache tokenCacheObject = this.createCacheObject(accessToken, userId);

        Utils.UserClientInfo clientInfo = Utils.getUserClientInfo(request);
        //根据从请求头 agent-type中分析出的客户端系统 获取clientTokenConfig
        CustomSecurityProperties.ClientTokenConfig clientTokenConfig = this.getClientTokenConfig(clientInfo);
        //存入redis, 根据自定义配置决定 凭证,刷新凭证有效期
        long tokenExpireSeconds = clientTokenConfig.getExpireTime().getSeconds();
        byte[] tokenKey = customRedisCacheService.serializeKey(CacheConstant.Strings.AUTH, accessToken.getToken());
        byte[] value = customRedisCacheService.serializeValue(() -> tokenCacheObject);
        byte[] refreshTokenKey = customRedisCacheService.serializeKey(CacheConstant.Strings.REFRESH_AUTH, accessToken.getRefreshToken());
        customRedisCacheService.execute(conn -> {
            conn.set(tokenKey, value);
            conn.expire(Objects.requireNonNull(tokenKey), tokenExpireSeconds);
            conn.set(refreshTokenKey, value);
            conn.expire(Objects.requireNonNull(refreshTokenKey), clientTokenConfig.getRefreshTokenExpireTime().getSeconds());
            return null;
        }, true);
        //存入在线用户信息
        onlineUserService.save(tokenCacheObject, clientInfo, clientTokenConfig.getType());
        return accessToken;
    }

    private CustomAccessToken refresh(CustomTokenCache customTokenCache, HttpServletRequest request) {
        CustomAccessToken accessToken = this.create(null, customTokenCache.getAccessToken());
        //创建token redis cache对象
        CustomTokenCache tokenCacheObject = this.createCacheObject(accessToken, customTokenCache.getUser().getId());

        Utils.UserClientInfo clientInfo = Utils.getUserClientInfo(request);
        //根据从请求头 agent-type中分析出的客户端系统 获取clientTokenConfig
        CustomSecurityProperties.ClientTokenConfig clientTokenConfig = this.getClientTokenConfig(clientInfo);
        //存入redis, 根据自定义配置决定 凭证,刷新凭证有效期
        long tokenExpireSeconds = clientTokenConfig.getExpireTime().getSeconds();
        byte[] tokenKey = customRedisCacheService.serializeKey(CacheConstant.Strings.AUTH, accessToken.getToken());
        byte[] value = customRedisCacheService.serializeValue(() -> tokenCacheObject);
        byte[] refreshTokenKey = customRedisCacheService.serializeKey(CacheConstant.Strings.REFRESH_AUTH, accessToken.getRefreshToken());
        customRedisCacheService.execute(conn -> {
            conn.set(tokenKey, value);
            conn.expire(Objects.requireNonNull(tokenKey), tokenExpireSeconds);
            //不刷新有效时间
            conn.setRange(refreshTokenKey, value, 0);
            return null;
        }, true);
        //存入在线用户信息
        onlineUserService.save(tokenCacheObject, clientInfo, clientTokenConfig.getType());
        return accessToken;
    }

    /**
     * 创建token
     *
     * @param scopes   作用域
     * @param oldToken 刷新token 传入旧token
     * @return 生成新token
     */
    private CustomAccessToken create(List<String> scopes, CustomAccessToken oldToken) {
        CustomAccessToken accessToken = new CustomAccessToken();
        //请求凭证
        accessToken.setToken(tokenProvider.generateToken());
        //刷新凭证
        accessToken.setRefreshToken(oldToken == null ? tokenProvider.generateToken() : oldToken.getRefreshToken());
        accessToken.setScope(oldToken == null ? scopes : oldToken.getScope());
        //bearer
        accessToken.setTokenType(oldToken == null ? tokenProvider.getTokenType() : oldToken.getTokenType());
        return accessToken;
    }

    private CustomTokenCache createCacheObject(CustomAccessToken accessToken, Long userId) {
        CustomTokenCache customTokenCache = new CustomTokenCache();
        customTokenCache.setAccessToken(accessToken);
        CurrentUser currentUser = systemUserService.getById(userId);
        customTokenCache.setUser(currentUser);
        return customTokenCache;
    }

    /**
     * 根据平台类型获取对应token配置
     *
     * @param clientInfo 客户端信息
     * @return UserClientType
     */
    private CustomSecurityProperties.ClientTokenConfig getClientTokenConfig(Utils.UserClientInfo clientInfo) {
        //todo  根据userAgent决定客户端类型
        UserAgent userAgent = clientInfo.getUserAgent();
        return tokenProvider.getClientTokenConfig(UserClientType.PC);
    }

    public void logout(HttpServletRequest request) {
        //获取请求凭证
        String accessToken = tokenProvider.getToken(request);
        if (StrUtil.isEmpty(accessToken)) {
            return;
        }
        byte[] tokenKey = customRedisCacheService.serializeKey(CacheConstant.Strings.AUTH, accessToken);
        customRedisCacheService.execute(conn -> {
            //获取 token 对应的缓存,pileline为true时获取不到值
            CustomTokenCache customTokenCache = customRedisCacheService.deserializeValue(conn.get(tokenKey));
            if (customTokenCache != null) {
                CustomAccessToken customAccessToken = customTokenCache.getAccessToken();
                byte[] refreshTokenKey = customRedisCacheService.serializeKey(CacheConstant.Strings.REFRESH_AUTH, customAccessToken.getRefreshToken());
                //移除缓存
                conn.del(tokenKey, refreshTokenKey);
            }
            return null;
        }, false);
    }

    public CurrentUserInfo currentUserInfo() {
        CurrentUser currentUser = CustomUserDetails.current();
        CurrentUserInfo userInfo = new CurrentUserInfo();
        BeanUtils.copyProperties(currentUser, userInfo);
        return userInfo;
    }
}
